baubs git
Commit a077dea68e332bae67a37691388eed873dce9c00
Parents : 4992c33
Author : Jan-Henrik Bruhn <jan-henrik.bruhn@offis.de>
Date : 2025-12-21T13:29:27+01:00
feature: Replace connection indicator with shadcn StatusIndicator
Replace the custom connection indicator dots with the shadcn StatusIndicator
component for better visual consistency and state indication.
Changes:
- Install status-indicator component from 8starlabs shadcn registry
- Add getStatusIndicatorState() helper to map MachineStateCategory to StatusIndicator states
- Replace connection indicator divs with StatusIndicator component in AppHeader
- Connection indicator now shows state-dependent colors:
- Green (active): Connected and ready, sewing, or complete
- Yellow (fixing): Connected and waiting for user action
- Red (down): Connected but interrupted or in error state
- Gray (idle): Disconnected
- Remove unused color prop from StatusIndicator component
The StatusIndicator provides animated visual feedback for different machine
states, making it easier for users to understand the current system status at
a glance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes
3 files changed, 122 insertions(+), 9 deletions(-)
Diff
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
index 2f1fe20..516b6ed 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader.tsx
@@ -3,7 +3,10 @@ import { useMachineStore } from "../stores/useMachineStore";
import { useUIStore } from "../stores/useUIStore";
import { WorkflowStepper } from "./WorkflowStepper";
import { ErrorPopoverContent } from "./ErrorPopover";
-import { getStateVisualInfo } from "../utils/machineStateHelpers";
+import {
+ getStateVisualInfo,
+ getStatusIndicatorState,
+} from "../utils/machineStateHelpers";
import {
CheckCircleIcon,
BoltIcon,
@@ -14,6 +17,7 @@ import {
} from "@heroicons/react/24/solid";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
+import StatusIndicator from "@/components/ui/status-indicator";
import { Popover, PopoverTrigger } from "@/components/ui/popover";
import {
Tooltip,
@@ -66,20 +70,18 @@ export function AppHeader() {
};
const StatusIcon = stateIcons[stateVisual.iconName];
+ // Get connection indicator state (idle when disconnected, state-dependent when connected)
+ const connectionIndicatorState = isConnected
+ ? getStatusIndicatorState(machineStatus)
+ : "idle";
+
return (
<TooltipProvider>
<header className="bg-gradient-to-r from-primary-600 via-primary-700 to-primary-800 dark:from-primary-700 dark:via-primary-800 dark:to-primary-900 px-4 sm:px-6 lg:px-8 py-3 shadow-lg border-b-2 border-primary-900/20 dark:border-primary-800/30 flex-shrink-0">
<div className="grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-4 lg:gap-8 items-center">
{/* Machine Connection Status - Responsive width column */}
<div className="flex items-center gap-3 w-full lg:w-[280px]">
- <div
- className="w-2.5 h-2.5 bg-success-400 rounded-full animate-pulse shadow-lg shadow-success-400/50"
- style={{ visibility: isConnected ? "visible" : "hidden" }}
- ></div>
- <div
- className="w-2.5 h-2.5 bg-gray-400 rounded-full -ml-2.5"
- style={{ visibility: !isConnected ? "visible" : "hidden" }}
- ></div>
+ <StatusIndicator state={connectionIndicatorState} size="sm" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h1 className="text-lg lg:text-xl font-bold text-white leading-tight">
diff --git a/src/components/ui/status-indicator.tsx b/src/components/ui/status-indicator.tsx
new file mode 100644
index 0000000..8e318e0
--- /dev/null
+++ b/src/components/ui/status-indicator.tsx
@@ -0,0 +1,84 @@
+import React from "react";
+import { cn } from "@/lib/utils";
+
+interface StatusIndicatorProps {
+ state: "active" | "down" | "fixing" | "idle";
+ label?: string;
+ className?: string;
+ size?: "sm" | "md" | "lg";
+ labelClassName?: string;
+}
+
+const getStateColors = (state: StatusIndicatorProps["state"]) => {
+ switch (state) {
+ case "active":
+ return { dot: "bg-green-500", ping: "bg-green-300" };
+ case "down":
+ return { dot: "bg-red-500", ping: "bg-red-300" };
+ case "fixing":
+ return { dot: "bg-yellow-500", ping: "bg-yellow-300" };
+ case "idle":
+ default:
+ return { dot: "bg-slate-700", ping: "bg-slate-400" };
+ }
+};
+
+const getSizeClasses = (size: StatusIndicatorProps["size"]) => {
+ switch (size) {
+ case "sm":
+ return { dot: "h-2 w-2", ping: "h-2 w-2" };
+ case "lg":
+ return { dot: "h-4 w-4", ping: "h-4 w-4" };
+ case "md":
+ default:
+ return { dot: "h-3 w-3", ping: "h-3 w-3" };
+ }
+};
+
+const StatusIndicator: React.FC<StatusIndicatorProps> = ({
+ state = "idle",
+ label,
+ className,
+ size = "md",
+ labelClassName,
+}) => {
+ const shouldAnimate =
+ state === "active" || state === "fixing" || state === "down";
+ const colors = getStateColors(state);
+ const sizeClasses = getSizeClasses(size);
+
+ return (
+ <div className={cn("flex items-center gap-2", className)}>
+ <div className="relative flex items-center">
+ {shouldAnimate && (
+ <span
+ className={cn(
+ "absolute inline-flex rounded-full opacity-75 animate-ping",
+ sizeClasses.ping,
+ colors.ping,
+ )}
+ />
+ )}
+ <span
+ className={cn(
+ "relative inline-flex rounded-full",
+ sizeClasses.dot,
+ colors.dot,
+ )}
+ />
+ </div>
+ {label && (
+ <p
+ className={cn(
+ "text-sm text-slate-700 dark:text-slate-300",
+ labelClassName,
+ )}
+ >
+ {label}
+ </p>
+ )}
+ </div>
+ );
+};
+
+export default StatusIndicator;
diff --git a/src/utils/machineStateHelpers.ts b/src/utils/machineStateHelpers.ts
index 8548bc6..231c84c 100644
--- a/src/utils/machineStateHelpers.ts
+++ b/src/utils/machineStateHelpers.ts
@@ -205,3 +205,30 @@ export function getStateVisualInfo(status: MachineStatus): StateVisualInfo {
return visualMap[category];
}
+
+/**
+ * Map machine state category to status indicator state.
+ * Returns the appropriate state for the StatusIndicator component.
+ */
+export function getStatusIndicatorState(
+ status: MachineStatus,
+): "active" | "down" | "fixing" | "idle" {
+ const category = getMachineStateCategory(status);
+
+ switch (category) {
+ case MachineStateCategory.IDLE:
+ return "active"; // Gray, no animation
+ case MachineStateCategory.ACTIVE:
+ return "active"; // Green with animation
+ case MachineStateCategory.WAITING:
+ return "fixing"; // Yellow with animation
+ case MachineStateCategory.COMPLETE:
+ return "active"; // Green with animation
+ case MachineStateCategory.INTERRUPTED:
+ return "down"; // Red with animation
+ case MachineStateCategory.ERROR:
+ return "down"; // Red with animation
+ default:
+ return "idle";
+ }
+}
Served by rngit 1.3.3 - Generated in 0.06s